import Decimal from 'decimal.js';

//  bignumber.js 升级到 decimal.js
// decimal.js支持三角函数
/**
 * 默认类型
 */
export interface NumberUtilsDefaultType {
  /**
   * 支持：add
   * 计算出NaN的时候返回的值
   */
  isNaNResult?: string;
  /**
   * 计算参数默认值
   */
  numParamsDefault: number;
  /**
   * 支持：add
   * NaN数字用0替换
   */
  numberNaNReplaceZero?: boolean;
  /**
   * 保留的结果
   */
  digits?: number;
  /**
   * 精度相等配置
   */
  equalDigits?: number | string;
  /**
   * 小数为0也保留精度
   */
  keepDigits?: boolean;
}

/**
 * 默认值
 */
export const NumberUtilsDefault: NumberUtilsDefaultType = {
  isNaNResult: '0',
  numParamsDefault: 0,
  numberNaNReplaceZero: true,
  digits: 2,
  equalDigits: 0.00000000000000001,
  keepDigits: false,
}

export type MyNum = string | number;

export type ComputeType = '+' | '-' | '/' | '*' | '**' | '%'


export class NumberUtils {

  static getBigNumber(value: number | string,nanRep?:number): Decimal {
    if(value === null || value === '' || value === undefined) {
      if(nanRep === undefined) {
        return new Decimal(NumberUtilsDefault.numParamsDefault)
      }
      return new Decimal(nanRep);
    }
    return new Decimal(value);
  }

  static isInt(value: string) {
    return NumberUtils.isIntNum(Number(value))
  }

  static isIntNum(value: number) {
    const bigNumber = NumberUtils.getBigNumber(value);
    return bigNumber.isInteger()
  }

  /**
   * 获取小数长度
   * @param num1
   * @param num2
   */
  static getMaxSmallLength(num1: string, num2: string): number {
    let smallLength = 0
    if (!NumberUtils.isInt(num1)) {
      smallLength = num1.split('.')[1].length
    }
    if (!NumberUtils.isInt(num2)) {
      const length = num1.split('.')[1].length;
      if (length > smallLength) {
        smallLength = length
      }
    }
    return smallLength
  }

  /**
   * 扩大多少倍
   * @param num1
   * @param count
   * @param expand
   */
  static expandNumber(num1: string, count: number, expand: number = 10): string {
    const bigNumber = NumberUtils.getBigNumber(num1);
    return bigNumber.mul(count).mul(expand).toString()
  }

  /**
   * 最大公因数
   * @param num1
   * @param num2
   */
  static commonMaxNum(num1: string, num2: string): number {
    const maxSmallLength = NumberUtils.getMaxSmallLength(num1, num2);
    if (maxSmallLength > 0) {
      num1 = NumberUtils.expandNumber(num1, maxSmallLength)
      num2 = NumberUtils.expandNumber(num2, maxSmallLength)
    }
    let n1 = NumberUtils.getBigNumber(num1)
    let n2 = NumberUtils.getBigNumber(num2)
    if (n1.equals(n2)) {
      return n1.toNumber()
    }
    // 声明变量sum记录整除2的次数
    let sum = 1;
    // 使用while循环 对两个整数用2分别求余
    while (n1.mod(2).equals(0) && n2.mod(2).equals(0)) {
      n1 = n1.div(2);
      n2 = n2.div(2);
      sum *= 2;
    }
    // 比较n1，n2的大小，使用解构赋值，使较大的数等于n1
    if (n1.minus(n2).lt(0.1)) {
      [n1, n2] = [n2, n1];
    }
    // 较大的数减较小的数，求差
    let poor = n1.minus(n2);
    // 使用while循环判断 差 是否等于n2, 如果不等于，则继续使较大数减较小数求差
    while (!poor.equals(n2)) {
      if (poor.minus(n2).lt(0.1)) {
        [n1, n2] = [n2, poor];
      } else {
        [n1, n2] = [poor, n2];
      }
      poor = n1.minus(n2);
    }
    // 若干个2 乘以 得出的差，就是最大公约数
    return poor.mul(sum).toNumber();
  }

  /**
   * 最大供应数
   * @param arr
   */
  static commonMaxNumArray(arr: string[]): number {
    let result = 0
    if (arr.length === 0) {
      return result
    }
    if (arr.length === 1) {
      return Number(arr[0])
    }
    if (arr.length > 1) {
      result = NumberUtils.commonMaxNum(arr[0], arr[1])
      for (let i = 2; i < arr.length; i++) {
        result = NumberUtils.commonMaxNum(result + '', arr[i])
      }
    }
    return result
  }

  /**
   * 运算
   * ** 表示指数
   * @param num1
   * @param num2
   * @param other
   * @param type
   */
  static compute(num1: string | number, num2: string | number,
                 other?: NumberUtilsDefaultType,
                 type: ComputeType = '+'
  ): string {
    const objDefault = Object.assign({}, NumberUtilsDefault, other) as NumberUtilsDefaultType
    let b1 = NumberUtils.getBigNumber(num1);
    if (b1.isNaN()) {
      if (objDefault.numberNaNReplaceZero) {
        b1 = NumberUtils.getBigNumber(0)
      } else {
        return NumberUtils.numberFixed(objDefault.isNaNResult, objDefault.digits, objDefault.keepDigits)
      }
    }
    let b2 = NumberUtils.getBigNumber(num2);
    if (b2.isNaN()) {
      if (objDefault.numberNaNReplaceZero) {
        b2 = NumberUtils.getBigNumber(0)
      } else {
        return NumberUtils.numberFixed(objDefault.isNaNResult, objDefault.digits, objDefault.keepDigits)
      }
    }
    let result = NumberUtils.getBigNumber(NaN)
    switch (type) {
      case "+":
        result = b1.plus(b2)
        break
      case "-":
        result = b1.minus(b2)
        break
      case "/":
        if (b2.isZero()) {
          return NumberUtils.numberFixed(objDefault.isNaNResult, objDefault.digits, objDefault.keepDigits)
        }
        result = b1.div(b2)
        break
      case "*":
        result = b1.times(b2)
        break
      case "**":
        result = b1.pow(b2)
        break
      case "%":
        if (b2.isZero()) {
          return NumberUtils.numberFixed(objDefault.isNaNResult, objDefault.digits, objDefault.keepDigits)
        }
        result = b1.mod(b2)
        break
    }
    if (result.isNaN()) {
      return NumberUtils.numberFixed(objDefault.isNaNResult, objDefault.digits, objDefault.keepDigits)
    }
    return NumberUtils.numberFixed(result, objDefault.digits, objDefault.keepDigits)
  }

  static computeList(nums: (string | number)[],
                     other?: NumberUtilsDefaultType,
                     type: ComputeType = '+'): string {
    if (nums.length === 0) {
      return NumberUtilsDefault.isNaNResult
    }
    if (nums.length === 1) {
      return NumberUtils.add(nums[0], 0, other)
    }
    const objDefault = Object.assign({}, NumberUtilsDefault, other) as NumberUtilsDefaultType
    let other1 = {
      ...objDefault,
      digits: -1
    }
    let result = NumberUtils.compute(nums[0], nums[1], other1, type)
    for (let i = 2; i < nums.length; i++) {
      result = NumberUtils.compute(result, nums[i], other1, type)
    }
    return NumberUtils.numberFixed(result, objDefault.digits, objDefault.keepDigits)
  }

  static numberFixed(result: Decimal | number | string,
                     digits: number = NumberUtilsDefault.digits,
                     keepDigits: boolean = NumberUtilsDefault.keepDigits): string {
    if (!(result instanceof Decimal)) {
      result = NumberUtils.getBigNumber(result)
    }
    if (digits === -1) {
      if (keepDigits) {
        return result.toFixed()
      }
      return NumberUtils.getBigNumber(result.toFixed()).toFixed()
    }
    if (keepDigits) {
      return result.toFixed(digits)
    }
    return NumberUtils.getBigNumber(result.toFixed(digits)).toFixed()
  }

  /**
   * 相加数据
   * @param num1
   * @param num2
   * @param other
   */
  static add(num1: string | number, num2: string | number, other?: NumberUtilsDefaultType): string {
    return NumberUtils.compute(num1, num2, other, '+')
  }

  /**
   * 相加数据
   * @param nums
   * @param other
   */
  static addList(nums: (string | number)[], other?: NumberUtilsDefaultType): string {
    return NumberUtils.computeList(nums, other, '+')
  }

  /**
   * 相减
   * num1 - num2
   * @param num1
   * @param num2
   * @param other
   */
  static sub(num1: string | number, num2: string | number, other?: NumberUtilsDefaultType): string {
    return NumberUtils.compute(num1, num2, other, '-')
  }

  /**
   * 相减数据
   * @param nums
   * @param other
   */
  static subList(nums: (string | number)[], other?: NumberUtilsDefaultType): string {
    return NumberUtils.computeList(nums, other, '-')
  }

  /**
   * 除法
   * num1 / num2
   * @param num1
   * @param num2
   * @param other
   */
  static div(num1: string | number, num2: string | number, other?: NumberUtilsDefaultType) {
    return NumberUtils.compute(num1, num2, other, '/')
  }

  /**
   * 乘法
   * num1 * num2
   * @param num1
   * @param num2
   * @param other
   */
  static mul(num1: string | number, num2: string | number, other?: NumberUtilsDefaultType) {
    return NumberUtils.compute(num1, num2, other, '*')
  }

  static mulList(nums: (string | number)[], other?: NumberUtilsDefaultType): string {
    return NumberUtils.computeList(nums, other, '*')
  }

  /**
   * 判断是否相等
   * @param num1
   * @param num2
   * @param digits 精度，相差多少认为也是对的
   */
  static isEqual(num1: string | number, num2: string | number, digits: number | string = NumberUtilsDefault.equalDigits) {
    const other = {
      ...NumberUtilsDefault,
    }
    let digitsStr = String(digits)
    const strings = digitsStr.split('.');
    if (strings.length === 2) {
      const length = strings[1].length;
      if (length > other.digits) {
        other.digits = length
      }
    }
    const result = NumberUtils.compute(num1, num2, other, '-')
    const zero = NumberUtils.compute(NumberUtils.getBigNumber(result).abs().toFixed(), digits, other, '-')
    return NumberUtils.getBigNumber(zero).lessThanOrEqualTo(0)
  }

  /**
   * 比较大小
   * @param num1
   * @param num2
   * @param digits 精度，相差多少认为也是对的
   */
  static compare(num1: string | number, num2: string | number, digits: number | string = NumberUtilsDefault.equalDigits) {
    if (NumberUtils.isEqual(num1, num2, digits)) {
      return 0
    }
    let a = NumberUtils.getBigNumber(num1)
    let b = NumberUtils.getBigNumber(num2)
    // a比b大
    if (a.greaterThan(b)) {
      return 1
    } else {
      return -1
    }
  }

  /**
   * 取得[n,m]范围随机数
   * n <= result <= m
   * @param n
   * @param m
   * @param digits
   */
  static randomFullClose(n: number, m: number, digits = NumberUtilsDefault.equalDigits): number {
    let result = Math.random() * (m + 1 - n) + n;
    while (result > m) {
      result = Math.random() * (m + 1 - n) + n;
    }
    return Number(result.toFixed(Number(digits)));
  }

  /**
   * 取得(n,m)范围随机数
   * @param n
   * @param m
   * @param digits
   */
  static randomFullOpen(n: number, m: number, digits = NumberUtilsDefault.equalDigits): number {
    let result = Math.random() * (m - n) + n;
    while (result == n) {
      result = Math.random() * (m - n) + n;
    }
    return Number(result.toFixed(Number(digits)));
  }

  /**
   * 取得(n,m]范围随机数
   * @param n
   * @param m
   * @param digits
   */
  static randomLeftOpen(n: number, m: number, digits = NumberUtilsDefault.equalDigits): number {
    let result = Math.random() * (m - n + 1) + n - 1;
    while (result < n) {
      result = Math.random() * (m - n + 1) + n - 1;
    }
    return Number(result.toFixed(Number(digits)));
  }

  /**
   * 取得 [ n, m )范围随机数
   * @param n
   * @param m
   * @param digits
   */
  static randomRightOpen(n: number, m: number, digits = NumberUtilsDefault.equalDigits): number {
    let result = Math.random() * (m - n) + n
    return Number(result.toFixed(Number(digits)));
  }

  /**
   * 向上取整，rank表示取整多少位，
   * rank = 1表示取整到个位数，
   * rank=10表示取整到十位数，以此类推
   * @param num
   * @param rank
   */
  static ceil(num: MyNum, rank: MyNum = 1): string {
    if (NumberUtils.isEqual(rank, 1)) {
      const bigNumber = NumberUtils.getBigNumber(num);
      return bigNumber.ceil().toString()
    } else {
      let s = NumberUtils.div(num, rank);
      const bigNumber = NumberUtils.getBigNumber(s);
      let s1 = bigNumber.ceil().toString()
      return NumberUtils.mul(s1, rank);
    }
  }

  /**
   * 函数接受三个参数：一个数字和两个范围边界。当边界值为-1时，我们将始终认为该边界是“开放”的，即不受限制。
   * 示例用法
   * const number = 5;
   * const range1 = [-1, 10]; // 没有下界，上界是10，所以5在范围内
   * const range2 = [1, -1]; // 下界是1，没有上界，所以5在范围内
   * const range3 = [3, 8]; // 下界是3，上界是8，所以5在范围内
   * const range4 = [6, 4]; // 无效范围（下界大于上界），始终返回false
   * const range5 = [-1, -1]; // 没有边界限制，始终返回true（但可能不符合所有用例）
   *
   * console.log(isNumberInRange(number, range1[0], range1[1])); // 输出: true
   * console.log(isNumberInRange(number, range2[0], range2[1])); // 输出: true
   * console.log(isNumberInRange(number, range3[0], range3[1])); // 输出: true
   * console.log(isNumberInRange(number, range4[0], range4[1])); // 输出: false
   * console.log(isNumberInRange(number, range5[0], range5[1])); // 输出: true，但请注意这可能不是所有情况下都期望的结果
   * @param number
   * @param lowerBound
   * @param upperBound
   */
  static isNumberInRange(number, lowerBound, upperBound) {
    // 如果lowerBound为-1，则认为没有下界限制
    if (lowerBound === -1) {
      // 只需检查number是否小于或等于upperBound（如果upperBound不是-1）
      return upperBound !== -1 && number <= upperBound;
    }

    // 如果upperBound为-1，则认为没有上界限制
    if (upperBound === -1) {
      // 只需检查number是否大于或等于lowerBound（如果lowerBound不是-1）
      return lowerBound !== -1 && number >= lowerBound;
    }

    // 如果两个边界都不是-1，则检查number是否在范围内
    return number >= lowerBound && number <= upperBound;
  }
}
